module IPFS.Multiaddr.IP.IPv4 (
  IPv4(..),
  fromOctets,
  fromOctets',
  fromTupleOctets,
  ipv4,
  any,
  localhost,
  decode,
  encode,
  parse
) where

import Prelude (class Eq, class Ord, class Show, bind, disj, pure, show, ($), (<>), (>>=))
import Data.ByteString as BS
import Data.Integral (fromIntegral)
import Data.List (List(..), (:))
import Data.Serialize.Get (Get,getWord32be)
import Data.Shift (shl, shr)
import Data.Tuple.Nested (Tuple4, tuple4, (/\))
import Data.UInt as U
import Data.Word (Word, Word32, Word8(..), parseWord8) 
import Text.Parsing.Parser (Parser, fail)
import Text.Parsing.Parser.Combinators (sepBy)
import Text.Parsing.Parser.String (char)
import Type.Quotient (mkQuotient)

newtype IPv4 = IPv4 { getIPv4 :: Word32 }

derive instance eqIPv4 :: Eq IPv4
derive instance ordIPv4 :: Ord IPv4

instance showIPv4 :: Show IPv4 where
  show  val@(IPv4 ip) = show aa <> "." <> show bb <> "." <> show cc <> "." <> show dd
   where
     a /\ b /\ c /\ d /\ unit = toOctets val
     aa::Int
     aa = fromIntegral a
     bb::Int
     bb = fromIntegral b
     cc::Int
     cc = fromIntegral c
     dd::Int
     dd = fromIntegral d

ipv4 :: Word8 -> Word8 -> Word8 -> Word8 -> IPv4
ipv4 = fromOctets

fromOctets :: Word8 -> Word8 -> Word8 -> Word8 -> IPv4
fromOctets a b c d = fromOctets' (fromIntegral a) (fromIntegral b) (fromIntegral c) (fromIntegral d)

fromOctets'::Word -> Word -> Word -> Word -> IPv4
fromOctets' a b c d = IPv4 { getIPv4 : w32}
   where
    w32::Word32
    w32 = (shl a  (U.fromInt 24)) `disj` 
          (shl b  (U.fromInt 16)) `disj`  
          (shl c  (U.fromInt 8))  `disj`  d

fromTupleOctets::(Tuple4 Word8 Word8 Word8  Word8) -> IPv4
fromTupleOctets (a /\ b /\ c /\ d /\ unit) = fromOctets a b c d 

toOctets :: IPv4 -> Tuple4 Word8 Word8 Word8 Word8
toOctets (IPv4 ip) = tuple4 (fromIntegral (shr ip.getIPv4 (U.fromInt 24)))
                            (fromIntegral (shr ip.getIPv4 (U.fromInt 16)))   
                            (fromIntegral (shr ip.getIPv4 (U.fromInt 8)))   
                            (fromIntegral ip.getIPv4)

encode::IPv4 -> BS.ByteString
encode i4 = BS.pack [mkQuotient $ U.toInt a,mkQuotient $ U.toInt b,mkQuotient $ U.toInt c ,mkQuotient $ U.toInt d]
  where
    (Word8 a) /\ (Word8 b) /\ (Word8 c) /\ (Word8 d) /\ unit = toOctets i4


decode::Get IPv4
decode = do
  w32 <- getWord32be
  pure $ IPv4 {getIPv4 : w32}

any::IPv4
any = IPv4 {getIPv4:fromIntegral 0}

loopback :: IPv4
loopback = fromOctets (fromIntegral 127) (fromIntegral 0) (fromIntegral 0) (fromIntegral 1)

localhost :: IPv4
localhost = loopback

parse::Parser String IPv4
parse =  words >>= toIPV4
  where
    words::Parser String (List Word8)
    words = parseWord8 `sepBy` (char '.')
    toIPV4::List Word8 -> Parser String IPv4
    toIPV4 (a:b:c:d:Nil) = pure $ fromOctets a b c d
    toIPV4          _   = fail "Parser IPV4 error"